#pragma once
#include <iostream>
#include <cstring>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>
#include <sys/epoll.h>
#include <cerrno>

static const uint16_t defaultport = 8082;
static const int backlog = 10;
static const int defaultsize = 10;
static const int reventnum = 64; // 一次从就绪队列拿出来多少节点

class EpollServer
{
public:
    EpollServer(const uint16_t port = defaultport, int timeout = 3000)
        : _listen_sockfd(-1), _epfd(-1), _port(port), _timeout(timeout)
    {
    }

    ~EpollServer()
    {
        close(_listen_sockfd);
    }

    // 初始化服务器
    void Init()
    {
        // 创建epoll模型
        _epfd = epoll_create(defaultsize);
        if (_epfd < 0)
        {
            std::cerr << "epoll_create fail:" << strerror(errno) << std::endl;
        }
        std::cout << "[epoll_create success]" << std::endl;

        // 创建监听套接字
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            std::cerr << "listensockfd socket fail" << std::endl;
            exit(-1);
        }
        std::cout << "[listensockfd socket success]" << std::endl;

        // 设置端口号复用
        int opt = 1;
        setsockopt(_listen_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

        // 绑定端口号
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_port);
        server.sin_addr.s_addr = INADDR_ANY;

        int retbind = bind(_listen_sockfd, (const struct sockaddr *)&server, sizeof(server));
        if (retbind < 0)
        {
            std::cerr << "listensockfd bind fail" << std::endl;
            exit(-1);
        }
        std::cout << "[listensockfd bind success]" << std::endl;

        // 开始监听
        int retlisten = listen(_listen_sockfd, backlog);
        if (retlisten < 0)
        {
            std::cerr << "listensockfd bind fail" << std::endl;
            exit(-1);
        }
        std::cout << "[listensockfd listen......]" << std::endl;
    }
    void Start()
    {
        struct epoll_event revents[reventnum];

        // 将监听套接字和他关心的事件添加到epoll模型中的红黑树中
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = _listen_sockfd; // 方便后期事件就绪时，获取fd

        int retctl = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listen_sockfd, &ev);
        if (retctl != 0)
        {
            std::cerr << "epoll_ctl listen_sockfd fail" << std::endl;
        }

        while (true)
        {
            int retwait = epoll_wait(_epfd, revents, reventnum, _timeout);
            if (retwait > 0)
            {
                std::cout << "event happened" << std::endl;
                Dispatcher(revents, retwait);
            }
            else if (retwait == 0)
            {
                std::cout << "time out" << std::endl;
            }
            else
            {
                std::cout << "epoll wait fail" << std::endl;
            }
        }
    }

    //事件派发器
    void Dispatcher(struct epoll_event *revents, int num)
    {
        for (int i = 0; i < num; i++)
        {
            // 获取哪个文件描述符的哪个事件就绪
            uint32_t event = revents[i].events;
            int fd = revents[i].data.fd;

            // 读事件
            if (event & EPOLLIN)
            {
                // 监听套接字
                if (fd == _listen_sockfd)
                {
                    // 获取新连接
                    struct sockaddr_in client;
                    memset(&client, 0, sizeof(client));
                    socklen_t len = sizeof(client);

                    int newsockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);
                    if (newsockfd < 0)
                    {
                        std::cerr << "accept fail" << std::endl;
                        continue;
                    }
                    std::cout << "[get a new link, newsockfd : " << newsockfd << "]" << std::endl;

                    // 将新连接的读事件添加到epoll模型中
                    struct epoll_event ev;
                    ev.events = EPOLLIN;
                    ev.data.fd = newsockfd;

                    int retctl = epoll_ctl(_epfd, EPOLL_CTL_ADD, newsockfd, &ev);
                    if (retctl != 0)
                    {
                        std::cerr << "epoll_ctl fd fail" << std::endl;
                    }
                }
                // 其他读事件就绪
                else
                {
                    char buffer[1024];
                    ssize_t n = read(fd, buffer, sizeof(buffer) - 1);
                    if (n > 0)
                    {
                        buffer[n] = '\0';
                        std::cout << "client say#" << buffer << std::endl;
                    }
                    else if (n == 0)
                    {
                        std::cerr << "client quit" << std::endl;
                        int retctl = epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
                        //文件描述符要先移除后关闭
                        if (retctl != 0)
                        {
                            std::cerr << "epoll_ctl fd fail" << std::endl;
                        }
                        close(fd);
                    }
                    else
                    {
                        std::cerr << "read fail" << std::endl;
                        int retctl = epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
                        if (retctl != 0)
                        {
                            std::cerr << "epoll_ctl fd fail" << std::endl;
                        }
                        close(fd);
                    }
                }
            }
            // 写事件
            else if (event & EPOLLOUT)
            {
            }
            // 其他事件
            else
            {
            }
        }
    }

private:
    int _listen_sockfd;
    int _epfd;
    uint16_t _port;
    int _timeout;
};

// 重复使用端口号函数
// backlog？
